// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author : dhjang

#pragma once

#include <cstdint>
#include <map>
#include <memory>
#include <utility>
#include <algorithm>
#include <fmt/format.h>

namespace coin2::strategy::linear_model {

struct OrderInfo {
  int64_t fetched_time;
  double qty;
};

// assuming passive only
// assuming orders are coming in time_sorted, subtle_reverse_order don't matter

// trait define what to do for order, which is filled long time ago
// e.g sell, buy_filled_order when it is filled 30min ago(y = 30min_return)

template <typename Traits>
class WorkingOrderManager {
 public:
  WorkingOrderManager() = default;
  explicit WorkingOrderManager(Traits traits)
    : traits_(traits),
      num_popped1_(0),
      num_popped2_(0),
      window_size_(0) {
  }
  ~WorkingOrderManager() = default;

  void ResizeWindow(int64_t window_size) {
    window_size_ = window_size;
  }

  void AddSubmitOrder(int64_t time, int64_t order_id, double qty) {
    submit_order_map_[order_id] = OrderInfo{time, qty};
  }

  // this is for eliminate reverse_side order
//  void AddReverseSubmitOrder(int64_t time, int64_t order_id, double qty){
//    if (fill_order_map_.size() > 0) {
//
//    }
//  }

  void AddFillOrder(int64_t time, int64_t order_id, double qty) {
    if (submit_order_map_.count(order_id) == 1) {
      if (fill_order_map_.count(order_id) == 0) {
        fill_order_map_[order_id] = submit_order_map_[order_id];
        fill_order_map_[order_id].qty = qty;
        traits_.OnPushed(fill_order_map_[order_id].fetched_time, qty);
      } else {
        fill_order_map_[order_id].qty = fill_order_map_[order_id].qty + qty;
        traits_.OnPushed(fill_order_map_[order_id].fetched_time, qty);
      }
    }
  }

  void UpdateTimeOnly(int64_t time) {
    bool empty = true;
    for (auto it = submit_order_map_.begin(); it != submit_order_map_.end(); ++it) {
      if (time - it->second.fetched_time < window_size_) {
        empty = false;
        if (it != submit_order_map_.begin()) {
          submit_order_map_.erase(submit_order_map_.begin(), it);
        }
        break;
      } else {
        num_popped1_++;
      }
    }
    if (empty) {
      submit_order_map_.clear();
    }
    empty = true;
    for (auto it = fill_order_map_.begin(); it != fill_order_map_.end(); ++it) {
      if (time - it->second.fetched_time < window_size_) {
        empty = false;
        if (it != fill_order_map_.begin()) {
          fill_order_map_.erase(fill_order_map_.begin(), it);
        }
        break;
      } else {
        num_popped2_++;
        if (StoreLastPopped) {
          last_popped_ = std::move(it->second);
        }
        traits_.OnPopped(it->second.fetched_time, it->second.qty);
      }
    }
    if (empty) {
      fill_order_map_.clear();
    }
  }

  Traits* traits() { return &traits_; }
  const Traits* traits() const { return &traits_; }

 private:
  Traits traits_;
  // map with key : proc_order_id, value : OrderInfo
  std::map<int64_t, OrderInfo> submit_order_map_;
  std::map<int64_t, OrderInfo> fill_order_map_;
  int64_t window_size_;
  int64_t target_position_ = 0;
  int64_t num_popped1_;
  int64_t num_popped2_;
  bool StoreLastPopped = false;
  OrderInfo last_popped_;
  double buffer_qty_ = 0;
};

struct TargetPosTraits {
  double target_position = 0;
  void OnClear() { target_position = 0; }
  // target_position is added when fill_order
  void OnPushed(int64_t time, double qty) { target_position += qty; }
  // target_position is reverted when (fill_order_age > window_size)
  void OnPopped(int64_t time, double qty) {
    target_position -= qty; }
};

// deal with the case when there was sell_signal few moment after buy(before buy_pos popped)
struct BufferTargetPosTraits {
  double target_position = 0;
  double fill_buffer_position = 0;
  double cancel_buffer_position = 0;
  void OnClear() { target_position = 0; }
  // target_position is added when fill_order
  void OnPushed(int64_t time, double qty) {
    if (target_position * qty >= 0) {
      target_position += qty;
    } else {
      if (target_position > 0) {
        fill_buffer_position += std::min(target_position, -qty);
        target_position += qty;
      } else {
        fill_buffer_position += std::max(target_position, -qty);
        target_position += qty;
      }
    }
  }
  // target_position is reverted when (fill_order_age > window_size)
  void OnPopped(int64_t time, double qty) {
    double elim_qty = 0, elim_qty1 = 0, elim_qty2 = 0;
    if (qty > 0) {
      elim_qty1 = std::max(std::min(qty, fill_buffer_position), 0.0);
      elim_qty2 = std::max(std::min(qty, -cancel_buffer_position), 0.0);
      if (elim_qty1 > 0) {
        elim_qty = elim_qty1;
        fill_buffer_position -= elim_qty1;
        cancel_buffer_position += elim_qty1;
      } else {
        elim_qty = elim_qty2;
        cancel_buffer_position += elim_qty2;
      }
    } else {
      elim_qty1 = std::min(std::max(qty, fill_buffer_position), 0.0);
      elim_qty2 = std::min(std::max(qty, -cancel_buffer_position), 0.0);
      if (elim_qty1 < 0) {
        elim_qty = elim_qty1;
        fill_buffer_position -= elim_qty1;
        cancel_buffer_position += elim_qty1;
      } else {
        elim_qty = elim_qty2;
        cancel_buffer_position += elim_qty2;
      }
    }
    target_position -= (qty - elim_qty);
  }
};

// class for getting target_position from working_order
//class TargetPosWorkingOrderManager:
//  public WorkingOrderManager<TargetPosTraits> {
// public:
//  TargetPosWorkingOrderManager() = default;
//  explicit TargetPosWorkingOrderManager(int64_t window_size) {
//    wom::ResizeWindow(window_size);
//  }
//  void ResizeWindow(int64_t window_size) {
//    wom::ResizeWindow(window_size);
//  }
//  double get_target_position() { return wom::traits()->target_position; }
//
// private:
//  typedef WorkingOrderManager<TargetPosTraits> wom;
//};
//
// class for getting target_position from working_order
class TargetPosWorkingOrderManager:
  public WorkingOrderManager<BufferTargetPosTraits> {
 public:
  TargetPosWorkingOrderManager() = default;
  explicit TargetPosWorkingOrderManager(int64_t window_size) {
    wom::ResizeWindow(window_size);
  }
  void ResizeWindow(int64_t window_size) {
    wom::ResizeWindow(window_size);
  }
  double get_target_position() { return wom::traits()->target_position; }

 private:
  typedef WorkingOrderManager<BufferTargetPosTraits> wom;
};

} // namespace coin2::strategy::linear_model


